using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

namespace HadronData
{
    public struct Bounds
    {
        private Vector2 m_vecMin, m_vecMax;

        public Bounds(Vector2 p_vecMin, Vector2 p_vecMax)
        {
            m_vecMin = p_vecMin;
            m_vecMax = p_vecMax;
        }

        public Bounds(float p_fMinX, float p_fMinY, float p_fMaxX, float p_fMaxY)
        {
            m_vecMin.X = p_fMinX;
            m_vecMin.Y = p_fMinY;
            m_vecMax.X = p_fMaxX;
            m_vecMax.Y = p_fMaxY;
        }

        public bool Contains(Vector2 p_vecPoint)
        {
            return p_vecPoint.X >= m_vecMin.X && p_vecPoint.X <= m_vecMax.X
                && p_vecPoint.Y >= m_vecMin.Y && p_vecPoint.Y <= m_vecMax.Y;
        }

        public Vector2 GetClosestPoint(Vector2 p_vecTestPoint)
        {
            Vector2 vecClosestPoint = p_vecTestPoint;
            vecClosestPoint.X = Math.Min(vecClosestPoint.X, m_vecMax.X);
            vecClosestPoint.X = Math.Max(vecClosestPoint.X, m_vecMin.X);
            vecClosestPoint.Y = Math.Min(vecClosestPoint.Y, m_vecMax.Y);
            vecClosestPoint.Y = Math.Max(vecClosestPoint.Y, m_vecMin.Y);
            return vecClosestPoint;
        }

        public bool Intersects(Bounds p_bounds)
        {
            return m_vecMax.X >= p_bounds.m_vecMin.X
                && m_vecMin.X <= p_bounds.m_vecMax.X
                && m_vecMax.Y >= p_bounds.m_vecMin.Y
                && m_vecMin.Y <= p_bounds.m_vecMax.Y;
        }

        public bool Intersects(Edge p_edge)
        {
            if (Contains(p_edge.Start))
                return true;
            if (Contains(p_edge.End))
                return true;

            Edge edge = new Edge(m_vecMin, new Vector2(m_vecMax.X, m_vecMin.Y));
            if (edge.Intersects(p_edge))
                return true;
            edge.Start = m_vecMax;
            if (edge.Intersects(p_edge))
                return true;
            edge.End = new Vector2(m_vecMin.X, m_vecMax.Y);
            if (edge.Intersects(p_edge))
                return true;
            edge.Start = m_vecMin;
            return edge.Intersects(p_edge);
        }

        public bool Intersects(Ray p_ray)
        {
            if (Contains(p_ray.Source))
                return true;

            Edge edge = new Edge(m_vecMin, new Vector2(m_vecMax.X, m_vecMin.Y));
            if (edge.Intersects(p_ray))
                return true;
            edge.Start = m_vecMax;
            if (edge.Intersects(p_ray))
                return true;
            edge.End = new Vector2(m_vecMin.X, m_vecMax.Y);
            if (edge.Intersects(p_ray))
                return true;
            edge.Start = m_vecMin;
            return edge.Intersects(p_ray);
        }

        public bool Intersects(Circle p_circle)
        {
            Vector2 vecOffset = GetClosestPoint(p_circle.Centre) - p_circle.Centre;
            return vecOffset.LengthSquared() <= p_circle.RadiusSquared;
        }

        public bool Intersects(Polygon p_polygon)
        {
            foreach (Vector2 vecVertex in p_polygon.Vertices)
                if (Contains(vecVertex))
                    return true;

            Edge edge = new Edge(m_vecMin, new Vector2(m_vecMax.X, m_vecMin.Y));
            if (p_polygon.Intersects(edge))
                return true;
            edge.Start = m_vecMax;
            if (p_polygon.Intersects(edge))
                return true;
            edge.End = new Vector2(m_vecMin.X, m_vecMax.Y);
            if (p_polygon.Intersects(edge))
                return true;
            edge.Start = m_vecMin;
            return p_polygon.Intersects(edge);
        }

        public void SetEmpty()
        {
            m_vecMin.X = float.MaxValue;
            m_vecMin.Y = float.MaxValue;
            m_vecMax.X = float.MinValue;
            m_vecMax.Y = float.MinValue;
        }

        public void ExtendTo(Vector2 p_vecPoint)
        {
            m_vecMin.X = Math.Min(m_vecMin.X, p_vecPoint.X);
            m_vecMin.Y = Math.Min(m_vecMin.Y, p_vecPoint.Y);
            m_vecMax.X = Math.Max(m_vecMax.X, p_vecPoint.X);
            m_vecMax.Y = Math.Max(m_vecMax.Y, p_vecPoint.Y);
        }

        public void Translate(Vector2 p_vecOffset)
        {
            m_vecMin += p_vecOffset;
            m_vecMax += p_vecOffset;
        }

        public void ExtendTo(Bounds p_bounds)
        {
            ExtendTo(p_bounds.Min);
            ExtendTo(p_bounds.Max);
        }

        public Vector2 Min
        {
            get { return m_vecMin; }
            set { m_vecMin = value; }
        }

        public Vector2 Max
        {
            get { return m_vecMax; }
            set { m_vecMax = value; }
        }

        public Vector2 Dimensions
        {
            get { return m_vecMax - m_vecMin; }
        }

        public Vector2 Centroid
        {
            get { return (m_vecMax + m_vecMin) * 0.5f; }
        }

        public float Width
        {
            get { return m_vecMax.X - m_vecMin.X; }
        }

        public float Height
        {
            get { return m_vecMax.Y - m_vecMin.Y; }
        }
    }
}
